﻿using FastApi.Attributes;
using FastEmit;
using System.Diagnostics;
using System.Reflection;

namespace FastApi
{
    internal class FastUtils
    {
        #region private method

        private static bool IsSimpleType(Type type)
        {
            if (type == typeof(string) || type == typeof(char) || type == typeof(char?))
            {
                return true;
            }
            if (type == typeof(int) || type == typeof(int?) || type == typeof(uint) || type == typeof(uint?))
            {
                return true;
            }
            if (type == typeof(long) || type == typeof(long?) || type == typeof(ulong) || type == typeof(ulong?))
            {
                return true;
            }
            if (type == typeof(DateTime) || type == typeof(DateTime?))
            {
                return true;
            }
            if (type == typeof(bool) || type == typeof(bool?))
            {
                return true;
            }
            if (type == typeof(decimal) || type == typeof(decimal?))
            {
                return true;
            }
            if (type == typeof(double) || type == typeof(double?))
            {
                return true;
            }
            if (type == typeof(float) || type == typeof(float?))
            {
                return true;
            }
            if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
            {
                return true;
            }
            if (type == typeof(short) || type == typeof(short?) || type == typeof(ushort) || type == typeof(ushort?))
            {
                return true;
            }
            if (type == typeof(byte) || type == typeof(byte?) || type == typeof(sbyte) || type == typeof(sbyte?))
            {
                return true;
            }
            if (type == typeof(Enum))
            {
                return true;
            }
            if (type == typeof(Guid) || type == typeof(Guid?))
            {
                return true;
            }
            if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
            {
                return true;
            }
            if (type == typeof(DateOnly) || type == typeof(DateOnly?))
            {
                return true;
            }
            if (type == typeof(TimeOnly) || type == typeof(TimeOnly?))
            {
                return true;
            }
            return false;
        }

        private static object GetTypeDefaultVal(Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return null;
            }
            return type.IsValueType ? Activator.CreateInstance(type) : null;
            //return ConstructorExtensions.CreateInstance(type);
        }

        private static List<FastParam> CreateFastParList(ParameterInfo[] parArray, Dictionary<RuntimeTypeHandle, FastParamCreator> dict, string method = "GET")
        {
            var list = new List<FastParam>();
            foreach (var item in parArray)
            {
                dict.TryGetValue(item.ParameterType.TypeHandle, out var creator);

                var isSimple = IsSimpleType(item.ParameterType);

                var attrs = item.GetCustomAttributes();

                var parType = FastParamType.Query;

                if (attrs.Any(a => a is FastBodyAttribute))
                {
                    parType = FastParamType.Body;
                }
                else if (attrs.Any(a => a is FastFormAttribute))
                {
                    parType = FastParamType.Form;
                }
                else if (attrs.Any(a => a is FastQueryAttribute))
                {
                    parType = FastParamType.Query;
                }
                else if (method == "POST")
                {
                    parType = FastParamType.Body;
                }

                var model = new FastParam(item.ParameterType, item.Name, isSimple, parType, creator);

                if (attrs.Any())
                {
                    model.AttributeList.AddRange(attrs);
                }

                list.Add(model);
            }
            return list;
        }

        internal static object CreateParamQuery(FastParam item, IQueryCollection query)
        {
            if (item.IsSimpleType)
            {
                string val = query[item.Name];

                if (item.Type == typeof(string))
                {
                    return val;
                }

                if (string.IsNullOrEmpty(val))
                {
                    return GetTypeDefaultVal(item.Type);
                }

                return TypeConverter.Get(item.Type, val);
            }

            return query.BindModel(item.Type);
        }

        internal static object CreateParamForm(FastParam item, IFormCollection form)
        {
            if (item.IsSimpleType)
            {
                string val = form[item.Name];

                if (item.Type == typeof(string))
                {
                    return val;
                }

                if (string.IsNullOrEmpty(val))
                {
                    return GetTypeDefaultVal(item.Type);
                }

                return TypeConverter.Get(item.Type, val);
            }

            return form.BindModel(item.Type);
        }

        #endregion

        #region Stopwatch

        private const long TicksPerMillisecond = 10000;

        private const long TicksPerSecond = TicksPerMillisecond * 1000;

        private static readonly double s_tickFrequency = (double)TicksPerSecond / Stopwatch.Frequency;

        public static double GetTotalMilliseconds(long start)
        {
#if !NET6_0
            return Stopwatch.GetElapsedTime(start).TotalMilliseconds;
#else
            return (Stopwatch.GetTimestamp() - start) * s_tickFrequency / TicksPerMillisecond;
#endif
        }

        #endregion

        internal static void AddType(string route, Type type, FastApp app)
        {
            if (app.FastModuleDict.ContainsKey(route))
            {
                throw new Exception($"class {type.FullName} route==>{route} already exists.");
            }

            #region 类处理

            var classInfo = ConstructorExtensions.Constructors(type).First();
            var classInvoker = ConstructorInfoExtensions.DelegateForCreateInstance(classInfo);
            var classParArray = classInfo.GetParameters();
            var classParList = CreateFastParList(classParArray, app.ParamCreatorDict);

            var ddd = type.CustomAttributes;

            //获取类特性
            var classAttrs = type.GetCustomAttributes(false).Where(w =>
                                  w.GetType().Name != "NullableContextAttribute" &&
                                  w.GetType().Name != "NullableAttribute"
                                ).ToArray();

            var classClear = false;

            if (classAttrs.Any(a => a is FastFilterClearAttribute))
            {
                classClear = true;
            }

            var fastModule = new FastModule(type, classInvoker, route, classClear);
            if (classParList.Count != 0)
            {
                fastModule.ParamList.AddRange(classParList);

                if (classParList.Any(a => a.NeedDispose))
                {
                    fastModule.NeedDispose = true;
                }

                if (classParList.Any(a => a.IsTran))
                {
                    fastModule.IsTran = true;
                }
            }

            if (classAttrs.Length != 0)
            {
                fastModule.AttributeList.AddRange(classAttrs);
            }

            //限流特性
            var moduleLimit = (FastRateLimitAttribute)classAttrs.FirstOrDefault(f => f.GetType() == typeof(FastRateLimitAttribute));
            if (moduleLimit != null)
            {
                fastModule.RateLimitCount = moduleLimit.Count;
                fastModule.RateLimitKey = moduleLimit.Key;
                fastModule.RateLimitMsg = moduleLimit.ErrMsg;
                fastModule.RateLimitCode = moduleLimit.ErrCode;

                if (string.IsNullOrEmpty(fastModule.RateLimitKey))
                {
                    fastModule.RateLimitKey = fastModule.RouteName;
                }
            }

            //Swagger分组处理
            var swagGroupAttr = classAttrs.FirstOrDefault(f => f.GetType() == typeof(FastSwagGroupAttribute)) as FastSwagGroupAttribute;
            if (swagGroupAttr != null)
            {
                fastModule.SwaggerGroup = swagGroupAttr.Name;
            }


            #endregion

            //获取所有方法
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance);
            foreach (var item in methods)
            {
                //跳过 get_ 和set_等特殊方法
                if (item.IsSpecialName)
                {
                    continue;
                }

                //获取方法特性
                var methodAttrs = item.GetCustomAttributes(true).Where(w =>
                                  w.GetType().Name != "NullableContextAttribute" &&
                                  w.GetType().Name != "AsyncStateMachineAttribute"
                                ).ToArray();

                //路由忽略，直接跳过
                if (methodAttrs.Any(a => a is FastIgnoreAttribute))
                {
                    continue;
                }

                var httpMethod = "GET";

                var postAttr = methodAttrs.FirstOrDefault(f => f is FastPostAttribute);
                var enableBuffer = false;
                if (postAttr != null)
                {
                    httpMethod = "POST";
                    enableBuffer = ((FastPostAttribute)postAttr).EnableBuffering;

                    if (app.EnableBuffer)
                    {
                        enableBuffer = true;
                    }
                }

                #region 参数处理

                var parArray = item.GetParameters();
                var parList = CreateFastParList(parArray, app.ParamCreatorDict, httpMethod);

                //参数存在Body或者Form 则判定为POST
                if (parList.Any(a => a.RequestType == FastParamType.Body || a.RequestType == FastParamType.Form))
                {
                    httpMethod = "POST";
                }

                #endregion

                #region 方法处理

                var clearFilter = false;

                if (methodAttrs.Any(a => a is FastFilterClearAttribute))
                {
                    clearFilter = true;
                }

                var returnType = item.ReturnType;
                FastActionType actionType = FastActionType.Default;
                FastActionTypeTask actionTypeTask = FastActionTypeTask.Default;

                #region FastRedirect

                if (methodAttrs.Any(a => a is FastRedirectAttribute))
                {
                    if (returnType == typeof(string))
                    {
                        actionType = FastActionType.Redirect;
                    }

                    if (returnType == typeof(Task<string>))
                    {
                        actionType = FastActionType.Redirect;
                        actionTypeTask = FastActionTypeTask.TaskT;
                    }

                    if (returnType == typeof(ValueTask<string>))
                    {
                        actionType = FastActionType.Redirect;
                        actionTypeTask = FastActionTypeTask.ValueTaskT;
                    }
                }

                #endregion

                #region FastView

                if (returnType == typeof(FastView))
                {
                    actionType = FastActionType.View;
                }

                if (returnType == typeof(Task<FastView>))
                {
                    actionType = FastActionType.View;
                    actionTypeTask = FastActionTypeTask.TaskT;
                }

                if (returnType == typeof(ValueTask<FastView>))
                {
                    actionType = FastActionType.View;
                    actionTypeTask = FastActionTypeTask.ValueTaskT;
                }

                #endregion

                #region Customer

                if (methodAttrs.Any(a => a is FastCustomerAttribute))
                {
                    actionType = FastActionType.Customer;
                }

                #endregion

                #region Task

                if (returnType == typeof(Task))
                {
                    actionTypeTask = FastActionTypeTask.Task;
                }
                else if (returnType.FullName.StartsWith("System.Threading.Tasks.Task"))
                {
                    actionTypeTask = FastActionTypeTask.TaskT;
                }

                #endregion

                #region ValueTask

                if (returnType == typeof(ValueTask))
                {
                    actionTypeTask = FastActionTypeTask.ValueTask;
                }
                else if (returnType.FullName.StartsWith("System.Threading.Tasks.ValueTask"))
                {
                    actionTypeTask = FastActionTypeTask.ValueTaskT;
                }

                #endregion

                var routeName = item.Name;
                var fastRoute = methodAttrs.FirstOrDefault(a => a is FastRouteAttribute) as FastRouteAttribute;
                if (fastRoute != null)
                {
                    routeName = fastRoute.Name;
                }
                else
                {
                    routeName = char.ToLower(routeName[0]) + routeName[1..];
                    //async路由处理
                    if (actionTypeTask != FastActionTypeTask.Default && routeName.Length > 5)
                    {
                        if (routeName.EndsWith("Async"))
                        {
                            routeName = routeName[..^5];
                        }
                    }
                }

                var path = route + routeName;
                var methodInvoker = FastEmit.MethodInfoExtensions.DelegateForCallMethod(item);

                var action = new FastAction(httpMethod, routeName, item.Name, clearFilter, methodInvoker,
                    item.IsStatic, returnType, actionType, actionTypeTask, enableBuffer, path);

                if (parList.Count != 0)
                {
                    action.ParamList.AddRange(parList);

                    if (parList.Any(a => a.NeedDispose))
                    {
                        action.NeedDispose = true;
                    }

                    if (parList.Any(a => a.IsTran))
                    {
                        action.IsTran = true;
                    }
                }

                if (methodAttrs.Length != 0)
                {
                    action.AttributeList.AddRange(methodAttrs);
                }

                //限流特性
                var actionLimit = methodAttrs.FirstOrDefault(f => f.GetType() == typeof(FastRateLimitAttribute)) as FastRateLimitAttribute;
                if (actionLimit != null)
                {
                    action.RateLimitCount = actionLimit.Count;
                    action.RateLimitKey = actionLimit.Key;
                    action.RateLimitMsg = actionLimit.ErrMsg;
                    action.RateLimitCode = actionLimit.ErrCode;

                    if (string.IsNullOrEmpty(action.RateLimitKey))
                    {
                        action.RateLimitKey = path;
                    }
                }
                else if (moduleLimit != null)
                {
                    action.RateLimitCount = fastModule.RateLimitCount;
                    action.RateLimitKey = fastModule.RateLimitKey;
                    action.RateLimitMsg = fastModule.RateLimitMsg;
                    action.RateLimitCode = fastModule.RateLimitCode;
                }

                action.IsJsonStream = methodAttrs.Any(a => a is FastJsonStreamAttribute);
                action.IsPostStream = methodAttrs.Any(a => a is FastPostStreamAttribute);
                action.IsDownload = methodAttrs.Any(a => a is FastDownloadAttribute);
                action.IsDownload = methodAttrs.Any(a => a is FastUploadAttribute);

                action.IsForm = action.ParamList.Any(a => a.RequestType == FastParamType.Form);
                if (action.IsUpload)
                {
                    action.IsForm = true;
                }

                //Path
                var pathAttr = methodAttrs.FirstOrDefault(f => f.GetType() == typeof(FastPathAttribute)) as FastPathAttribute;
                if (pathAttr != null)
                {
                    action.PathName = pathAttr.Name;
                }

                #endregion

                if (fastModule.ActionDict.ContainsKey(routeName.ToLower()))
                {
                    throw new Exception($"class {type.FullName} method name==>{item.Name} already exists.");
                }

                fastModule.ActionDict.Add(routeName.ToLower(), action);
            }

            app.FastModuleDict.Add(route, fastModule);
        }

    }
}
